import pandas as pd
import numpy as np

from core.constant import *
from conf import conf
from tools.k_lines import value2px, px2value
import copy


class EleModel:
    """元素窗口实体类父类"""
    def __init__(self, super_model, win_size, show_x_axis, show_legend):
        self.super_model = super_model
        self.draw_data: pd.DataFrame = None
        self.column_ls = None
        self.factor_ls = None
        self.win_size = win_size
        self.ele_size = [0, 0]
        self.x_axis_size = [0, 0]
        self.y_axis_size = [0, 0]

        self.show_x_axis = show_x_axis
        self.show_y_axis = conf.KChart.show_y_label.value
        self.show_legend = show_legend
        self.max_min_val_ls = [0, 0]

        self.x_coordinate = None
        self.factor_px_df = None
        self.k_px_df = None
        self.volume_px_df = None
        self.sub_c_o_sr = None

        # 个性风格配置参数
        self.k_blank = 2
        self.k_shadow = 1
        self.k_cir = 2
        self.k_line_size = 1  # k线影线size
        self.lines_size = 1  # 线条size

        self.x_axis_text_ls = []
        self.x_axis_px_ls = []

        self.y_axis_text_ls = []
        self.y_axis_px_ls = []

        self.legend_ls = []  # factor, color, type_fig
        self.layout_info(self.win_size)

    def sec_deal(self, draw_data):
        self.load_draw_data(draw_data)
        self.pre_deal()
        self.factor_info()
        self.x_axis_info()
        self.y_axis_info()

    def layout_info(self, win_size):
        self.win_size = win_size
        # 预先布局ele、x、y
        self.ele_size = copy.deepcopy(win_size)
        if self.show_x_axis:
            self.ele_size[1] -= conf.KChart.x_axis_width.value
        if self.show_y_axis:
            self.ele_size[0] -= conf.KChart.y_axis_width.value

        self.x_axis_size = [self.ele_size[0], win_size[1]-self.ele_size[1]]
        self.y_axis_size = [win_size[0]-self.ele_size[0], self.ele_size[1]]

    def load_draw_data(self, draw_data):
        if len(draw_data) > self.super_model.console_model.k_max_num:
            raise ValueError("加载的绘制数据长度大于当前窗口可容纳的最大长度。")
        self.draw_data = draw_data
        self.column_ls = self.draw_data.columns.tolist()
        self.factor_ls = [x for x in self.column_ls if x not in conf.KChart.not_factor_ls.value]
        if len(self.factor_ls) > len(conf.KChart.factor_order_color_ls.value):
            raise ValueError("预备颜色方案数量小于备选因子数量。")

    def pre_deal(self):
        # 检查是否有d数据
        if self.draw_data.index.name != "datetime":
            raise ValueError("传入元素窗口的绘图数据不包含以datetime为名的索引。")

    def factor_info(self):
        # 生成因子绘制像素信息
        factor_px_df: pd.DataFrame = self.draw_data[self.factor_ls]
        self.factor_px_df = factor_px_df.apply(value2px, max_min_val_ls=self.max_min_val_ls, width=self.ele_size[1])

    def add_legend(self, factor, color, factor_show_category: FactorShowCategory):
        self.legend_ls.append([factor, color, factor_show_category])

    def x_axis_info(self):
        # 计算x坐标 ------------------------------------------------
        if self.show_x_axis:
            # 计算x坐标 -----------------------------
            k_num = len(self.draw_data)  # 当前窗口绘制k线的数量
            # 每根k线对应的像素值
            k_px = self.super_model.console_model.k_cont_width
            # 一个坐标轴绘制间隔内的k线数量
            k_num_interval = conf.KChart.x_axis_interval.value // k_px
            # 获取绘制点坐标及其数值
            draw_px_ls = []
            draw_text_ls = []
            draw_num = k_num // k_num_interval  # 获取最多绘制数量
            for i in range(draw_num):
                if i == draw_num - 1:
                    continue  # 最后一个不绘制，为了美观
                # 绘制文本的坐标
                text_px_x = (i + 1) * k_num_interval * k_px - k_px / 2
                text_px_y = conf.KChart.x_axis_width.value / 2
                draw_px_ls.append([text_px_x, text_px_y])
                # 绘制文本的数值，此时记录datetime类型的时间数据
                text_dt = self.draw_data.iloc[(i + 1) * k_num_interval].name  # 定位到该位置对应的k线数据并获取相应series的name
                draw_text_ls.append(text_dt)
            # 处理draw_text_ls，判断k线的级别
            interval_ts_ls = []
            for i, o in enumerate(draw_text_ls):
                if i == 0:
                    continue
                interval_ts_ls.append(draw_text_ls[i].timestamp() - draw_text_ls[i - 1].timestamp())
            # 计算众数
            mode_values = np.unique(interval_ts_ls, return_counts=True)
            interval_ts = mode_values[0][mode_values[1].argmax()]

            if interval_ts < 60 * 60 * 24:  # 如果小于一天
                # 展示 月-日 时：分
                for i, o in enumerate(draw_text_ls):
                    draw_text_ls[i] = o.strftime("%m-%d %H:%M")
            else:
                # 展示 年-月-日
                for i, o in enumerate(draw_text_ls):
                    draw_text_ls[i] = o.strftime("%Y-%m-%d")
            self.x_axis_text_ls = draw_text_ls
            self.x_axis_px_ls = draw_px_ls

            # # 画到图标上
            # for i, o in enumerate(draw_text_ls):
            #     text_x = draw_px_ls[i][0]
            #     text_y = draw_px_ls[i][1]
            #     self.canvas.create_text(text_x, text_y, text=o, font=("Purisa", 10), fill="black")
            #     # 绘制|表示刻度
            #     self.canvas.create_text(text_x, 0, text='|', font=("Purisa", 10), fill="black")

    def y_axis_info(self):
        # 计算y坐标 -------------------------------------------
        self.y_axis_text_ls = []
        self.y_axis_px_ls = []
        if self.show_y_axis:
            # 最大绘制y坐标文本数量
            y_text_draw_num = self.y_axis_size[1] // conf.KChart.y_axis_interval.value
            # 确定0值
            if self.max_min_val_ls[0] > 0 > self.max_min_val_ls[1]:
                self.y_axis_text_ls.append("0")
                px_0 = value2px(0, self.max_min_val_ls, self.y_axis_size[1])
                self.y_axis_px_ls.append([conf.KChart.y_axis_width.value // 2, px_0])
                px_move = px_0
                while True:
                    px_move -= conf.KChart.y_axis_interval.value
                    if px_move < 0:
                        break
                    self.y_axis_text_ls.append(str(px2value(px_move, self.max_min_val_ls, self.y_axis_size[1])))
                    self.y_axis_px_ls.append([conf.KChart.y_axis_width.value // 2, px_move])
                px_move = px_0
                while True:
                    px_move += conf.KChart.y_axis_interval.value
                    if px_move > self.ele_size[1]:
                        break
                    self.y_axis_text_ls.append(str(px2value(px_move, self.max_min_val_ls, self.y_axis_size[1])))
                    self.y_axis_px_ls.append([conf.KChart.y_axis_width.value // 2, px_move])
            else:
                for i in range(y_text_draw_num):
                    px_move = (i + 1) * conf.KChart.y_axis_interval.value
                    value = px2value(px_move, self.max_min_val_ls, self.y_axis_size[1])
                    if np.isnan(value):
                        self.y_axis_text_ls.append('')
                    else:
                        self.y_axis_text_ls.append(str(value))
                    self.y_axis_px_ls.append([conf.KChart.y_axis_width.value // 2, px_move])


class KLinesModel(EleModel):
    def __init__(self, super_model, win_size, show_x_axis, show_legend):
        super().__init__(super_model, win_size, show_x_axis, show_legend)

    def load_draw_data(self, draw_data):
        super().load_draw_data(draw_data)
        self.x_coordinate = self.draw_data.index
        # 指示涨跌
        self.sub_c_o_sr = self.draw_data['close'] - self.draw_data['open']

    def pre_deal(self):
        super().pre_deal()

        # 检查是否有ohlc数据
        if not all(x in self.column_ls for x in ["open", "high", "low", "close"]):
            raise ValueError("K线所需数据不完整。")
        # 计算窗口最大最小值
        self.max_min_val_ls = [self.draw_data["high"].max(), self.draw_data["low"].min()]

    def k_px_info(self):
        # K线字段
        k_ls = ["open", "high", "low", "close"]
        col_ls = self.draw_data.columns.tolist()
        sig_ls = []
        for mem in SigType:
            if mem.value in col_ls:
                sig_ls.append(mem.value)
        k_ls += sig_ls
        # 生成因子绘制像素信息
        k_px_df: pd.DataFrame = self.draw_data[k_ls]
        self.k_px_df = k_px_df.apply(value2px, max_min_val_ls=self.max_min_val_ls, width=self.ele_size[1])


class VolumeModel(EleModel):
    def __init__(self, super_model, win_size, show_x_axis, show_legend):
        super().__init__(super_model, win_size, show_x_axis, show_legend)

    def load_draw_data(self, draw_data):
        super().load_draw_data(draw_data)
        self.x_coordinate = self.super_model.k_lines_model.x_coordinate
        # 指示涨跌
        self.sub_c_o_sr = self.super_model.k_lines_model.sub_c_o_sr

    def pre_deal(self):
        super().pre_deal()

        # 检查是否有v数据
        if not ("volume" in self.column_ls):
            raise ValueError("交易量图缺少volume数据。")
        # 计算窗口最大最小值
        self.max_min_val_ls = [self.draw_data["volume"].max(), 0]

    def volume_px_info(self):
        # 生成因子绘制像素信息
        volume_px_df: pd.DataFrame = self.draw_data[["volume"]]
        self.volume_px_df = volume_px_df.apply(value2px, max_min_val_ls=self.max_min_val_ls, width=self.ele_size[1])


class FactorModel(EleModel):
    def __init__(self, super_model, win_size, show_x_axis, show_legend):
        super().__init__(super_model, win_size, show_x_axis, show_legend)

    def load_draw_data(self, draw_data):
        super().load_draw_data(draw_data)
        self.x_coordinate = self.super_model.k_lines_model.x_coordinate
        # 指示涨跌
        self.sub_c_o_sr = self.super_model.k_lines_model.sub_c_o_sr

    def pre_deal(self):
        super().pre_deal()

        # 计算窗口最大最小值
        self.max_min_val_ls = [self.draw_data.max().max(), self.draw_data.min().min()]





